/*************************************************
* Title：TinyToolKit
* Author：Opportunity 
* Describe：构建AB包
* CreateTime：2020-05-27 10:41:17
* Version：1.0
* Modify Recorder：
*************************************************/

using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using TinyToolKit;
using TinyToolKit.AssetLoad;
using Random = UnityEngine.Random;

namespace TinyToolKitEditor
{
    public class BuildAssetBundle
    {
        private static string CONFIG_Name = "ABConfig";

        //存储所有文件夹的AB包，Key是AB包名，Value是文件夹路径
        private static Dictionary<string, string> allFileDir = new Dictionary<string, string>();

        //存储所有资源的路径，用于过滤冗余的资源
        private static List<string> allFileABPath = new List<string>();

        //存储单个Prefab的AB包,Key是Prefab的名字，Value是所有的依赖资源
        private static Dictionary<string, List<string>> allPrefabs = new Dictionary<string, List<string>>();

        //存储所有有效的资源路径（文件夹和预制体的路径），及项目中直接需要加载的资源
        private static List<string> usedAssetsPath = new List<string>();

        private static TinyToolEditorSettingAsset settingAsset;

        public static void Build(TinyToolEditorSettingAsset setting, BuildTarget buildTarget, BuildAssetBundleOptions compressType)
        {
            allFileDir.Clear();
            allFileABPath.Clear();
            allPrefabs.Clear();
            usedAssetsPath.Clear();

            if (setting == null) return;
            settingAsset = setting;

            foreach (string dirPath in settingAsset.allAssetFileDirPath)
            {
                string abName = Path.GetFileNameWithoutExtension(dirPath);
                if (allFileDir.ContainsKey(abName) == false)
                {
                    allFileDir.Add(abName, dirPath);
                    allFileABPath.Add(dirPath);
                    usedAssetsPath.Add(dirPath);
                }
                else
                    Debug.LogErrorFormat("AB包配置名字{0}重复", abName);
            }

            if (settingAsset.allPrefabDirPath.Count > 0)
            {
                //获取所有Prefab文件夹内所有的Prefab的GUID
                string[] allPrefabGUIDs = AssetDatabase.FindAssets("t:Prefab", settingAsset.allPrefabDirPath.ToArray());
                for (int i = 0; i < allPrefabGUIDs.Length; i++)
                {
                    //根据GUID获取预制体的路径
                    string path = AssetDatabase.GUIDToAssetPath(allPrefabGUIDs[i]);
                    usedAssetsPath.Add(path);
                    if (ContainsPath(path) == false)
                    {
                        //获取该Prefab所有的依赖资源的路径
                        string[] allDependencies = AssetDatabase.GetDependencies(path);
                        //存储还没有被打包的依赖资源的路径
                        List<string> allDependenciesPath = new List<string>();

                        for (int j = 0; j < allDependencies.Length; j++)
                        {
                            //判断预制体依赖的资源(非脚本文件)是否已经被打包
                            if (ContainsPath(allDependencies[j]) == false && allDependencies[j].EndsWith(".cs") == false)
                            {
                                //将该依赖资源路径添加再用于过滤冗余的列表中
                                allFileABPath.Add(allDependencies[j]);
                                //将所有还没有被打包过的依赖资源路径添加到列表中
                                allDependenciesPath.Add(allDependencies[j]);
                            }
                        }

                        //根据路径获取预制体对象
                        GameObject obj = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                        //将预制体名字作为AB包，并将预制体所有依赖项存储再字典中
                        if (allPrefabs.ContainsKey(obj.name))
                            Debug.LogErrorFormat("存在重名的Prefab:{0}", obj.name);
                        else
                            allPrefabs.Add(obj.name, allDependenciesPath);
                    }

                    EditorUtility.DisplayProgressBar("正在查找Prefabs", "Prefab Path:" + path,
                        i * 1.0f / allPrefabGUIDs.Length);
                }
            }

            //为所有的文件夹设置AB包名
            foreach (string name in allFileDir.Keys)
            {
                SetABName(name, allFileDir[name]);
            }

            //为所有预制体及其未打包的依赖项设置AB包名
            foreach (string name in allPrefabs.Keys)
            {
                SetABName(name, allPrefabs[name]);
            }

            string buildPath = settingAsset.buildSavePath + "/AssetBundle";
            //构建AB包
            BuildAB(buildTarget, compressType, buildPath, settingAsset.dataFormat);

            //清空所有的AB名
            if (settingAsset.isClearAllABName)
            {
                string[] oldABNames = AssetDatabase.GetAllAssetBundleNames();
                for (int i = 0; i < oldABNames.Length; i++)
                {
                    AssetDatabase.RemoveAssetBundleName(oldABNames[i], true);
                    EditorUtility.DisplayProgressBar("清除AB包名", "AB Name:" + oldABNames[i], i * 1.0f / oldABNames.Length);
                }
            }

            //最后再进行资源刷新操作 
            AssetDatabase.Refresh();
            EditorUtility.ClearProgressBar();

            EditorUtility.DisplayDialog("提示", "AB包打包结束！", "确认");

            Application.OpenURL("file://" + buildPath);
        }

        private static void BuildAB(BuildTarget buildTarget, BuildAssetBundleOptions compressType, string buildPath, DataFormat dataFormat)
        {
            //根据AB包名，存储资源路径的字典，用于存储配置表的数据
            string[] allBundleNames = AssetDatabase.GetAllAssetBundleNames();
            //Key存储的是资源路径，Value存储的AB包名
            Dictionary<string, string> resPathDic = new Dictionary<string, string>();

            for (int i = 0; i < allBundleNames.Length; i++)
            {
                string[] allBundlePath = AssetDatabase.GetAssetPathsFromAssetBundle(allBundleNames[i]);

                for (int j = 0; j < allBundlePath.Length; j++)
                {
                    //过滤脚本文件
                    if (allBundlePath[j].EndsWith(".cs")) continue;
                    if (IsUsedAsset(allBundlePath[j]))
                        resPathDic.Add(allBundlePath[j], allBundleNames[i]);
                }
            }

            string buildTargetPath = buildPath + "/" + Enum.GetName(typeof(BuildTarget), buildTarget);
            string configSavePath = buildTargetPath + "/ABConfig";
            if (Directory.Exists(buildTargetPath) == false)
                Directory.CreateDirectory(buildTargetPath);

            //根据AB包名，删除未使用的AB包
            if (settingAsset.deleteUnusedAB)
                DeleteUnusedAB(allBundleNames, buildTargetPath);

            //生成自定义的配置表
            CreateABConfig(resPathDic, configSavePath, dataFormat);

            //构建AB包
            BuildPipeline.BuildAssetBundles(buildTargetPath, compressType, buildTarget);

            //创建版本配置表
            if (settingAsset.spawnVersion)
                CreateVersion(allBundleNames, buildTargetPath, configSavePath, dataFormat);

            //删除所有的Manifest文件
            if (settingAsset.deleteABManifest)
                DeleteABManifest(buildTargetPath);

            //对AB资源进行加密
            if (settingAsset.encrypt)
                AssetBundleEncrypt(buildTargetPath, 18);
        }

        private static void DeleteUnusedAB(string[] allBundleNames, string buildTargetPath)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(buildTargetPath);
            FileInfo[] files = dirInfo.GetFiles("*", SearchOption.AllDirectories);
            for (int i = 0; i < files.Length; i++)
            {
                if (ContainsABName(files[i].Name, allBundleNames) || files[i].Name.EndsWith(".meta"))
                    continue;
                if (File.Exists(files[i].FullName))
                    File.Delete(files[i].FullName);
            }
        }

        private static void CreateABConfig(Dictionary<string, string> resPathDic, string configSavePath, DataFormat dataFormat)
        {
            //准备配置表的数据
            AssetBundleConfig config = new AssetBundleConfig();
            config.ABList = new List<ABBase>();

            foreach (string path in resPathDic.Keys)
            {
                //临时存储依赖的AB包名
                List<string> tempABNameList = new List<string>();
                string[] resDependencies = AssetDatabase.GetDependencies(path);
                for (int i = 0; i < resDependencies.Length; i++)
                {
                    string tempPath = resDependencies[i];
                    //过滤掉自身及脚本文件
                    if (tempPath == path || tempPath.EndsWith(".cs")) continue;

                    string abName = string.Empty;
                    if (resPathDic.TryGetValue(tempPath, out abName))
                    {
                        //过滤掉自己AB包内已经包含的资源
                        if (abName == resPathDic[path]) continue;

                        //添加其他AB包资源的依赖
                        if (tempABNameList.Contains(abName) == false)
                            tempABNameList.Add(abName);
                    }
                }

                ABBase abBase = new ABBase(path, CRC32.GetCRC32(path), resPathDic[path], Path.GetFileName(path), settingAsset.encrypt, tempABNameList);
                config.ABList.Add(abBase);
            }

            SaveConfig(config, configSavePath, CONFIG_Name, dataFormat);
        }

        private static void SaveConfig(AssetBundleConfig config, string savePath, string configName, DataFormat dataFormat)
        {
            string filePath;
            switch (dataFormat)
            {
                case DataFormat.Json:
                    filePath = savePath + "/" + configName + ".json";
                    var configJson = TinyUtility.Json.ToJson(config);
                    TinyUtility.File.SaveTextFile(filePath, configJson);
                    break;
                case DataFormat.Encrypt:
                    filePath = savePath + "/" + configName + ".txt";
                    EncryptDecryptBase encryptDecrypt = new EncryptDecrypt_Caesar();
                    var key = encryptDecrypt.Encrypt(TinyUtility.Json.ToJson(config));
                    TinyUtility.File.SaveTextFile(filePath, key);
                    break;
            }
        }

        private static void CreateVersion(string[] abNames, string buildTargetPath, string configSavePath, DataFormat dataFormat)
        {
            ABVersion abVersion = new ABVersion();

            for (int i = 0; i < abNames.Length; i++)
            {
                string abName = abNames[i];

                if (settingAsset.assetBundleVariant.IsNullOrEmpty())
                {
                    string path = Path.Combine(buildTargetPath, abName);
                    abVersion.abVersionItems.Add(new ABVersionItem(abName, MD5Ctr.GetMD5(path)));
                }
                else
                {
                    if (abName.Contains("." + settingAsset.assetBundleVariant))
                    {
                        string path = Path.Combine(buildTargetPath, abName);
                        abVersion.abVersionItems.Add(new ABVersionItem(abName, MD5Ctr.GetMD5(path)));
                    }
                }
            }

            string filePath;
            switch (dataFormat)
            {
                case DataFormat.Json:
                    filePath = configSavePath + "/Version.json";
                    var configJson = TinyUtility.Json.ToJson(abVersion);
                    TinyUtility.File.SaveTextFile(filePath, configJson);
                    break;
                case DataFormat.Encrypt:
                    filePath = configSavePath + "/Version.txt";
                    EncryptDecryptBase encryptDecrypt = new EncryptDecrypt_Caesar();
                    var key = encryptDecrypt.Encrypt(TinyUtility.Json.ToJson(abVersion));
                    TinyUtility.File.SaveTextFile(filePath, key);
                    break;
            }
        }

        private static void DeleteABManifest(string buildTargetPath)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(buildTargetPath);
            FileInfo[] files = dirInfo.GetFiles("*.manifest");
            for (int i = 0; i < files.Length; i++)
            {
                if (File.Exists(files[i].FullName))
                    File.Delete(files[i].FullName);
            }

            string targetFile = buildTargetPath + "/" + Path.GetFileNameWithoutExtension(buildTargetPath);
            if (File.Exists(targetFile))
                File.Delete(targetFile);
        }

        //对AB包进行简单的加密措施
        private static void AssetBundleEncrypt(string buildTargetPath, int offset)
        {
            DirectoryInfo dirInfo = new DirectoryInfo(buildTargetPath);
            FileInfo[] files = dirInfo.GetFiles("*." + settingAsset.assetBundleVariant);
            for (int i = 0; i < files.Length; i++)
            {
                var data = File.ReadAllBytes(files[i].FullName);
                byte[] buffer = new byte[data.Length + offset];
                for (int j = 0; j < offset; j++)
                {
                    buffer[j] = (byte)Random.Range(0, 255);
                }

                Array.Copy(data, 0, buffer, offset, data.Length);
                using (var myStream = new FileStream(files[i].FullName, FileMode.Create))
                {
                    myStream.Write(buffer, 0, buffer.Length);
                }
            }
        }

        //设置AB包名
        private static void SetABName(string abName, string path)
        {
            AssetImporter assetImporter = AssetImporter.GetAtPath(path);
            if (assetImporter != null)
            {
                assetImporter.assetBundleName = abName;
                if (settingAsset.assetBundleVariant.IsNullOrEmpty() == false)
                    assetImporter.assetBundleVariant = settingAsset.assetBundleVariant;
            }
            else
                Debug.LogErrorFormat("不存在此路径的文件：{0}", path);
        }

        //设置AB包名
        private static void SetABName(string abName, List<string> path)
        {
            for (int i = 0; i < path.Count; i++)
            {
                SetABName(abName, path[i]);
            }
        }

        private static bool IsUsedAsset(string path)
        {
            for (int i = 0; i < usedAssetsPath.Count; i++)
            {
                if (path.StartsWith(usedAssetsPath[i]))
                    return true;
            }

            return false;
        }

        //判断abNames中是否包含name
        private static bool ContainsABName(string name, string[] abNames)
        {
            for (int i = 0; i < abNames.Length; i++)
            {
                if (abNames[i] == name)
                    return true;
            }

            return false;
        }

        //判断allFileABPath是否已经包含了该路径的资源
        private static bool ContainsPath(string path)
        {
            for (int i = 0; i < allFileABPath.Count; i++)
            {
                if (allFileABPath[i] == path ||
                    (path.StartsWith(allFileABPath[i]) && (path.Replace(allFileABPath[i], "")[0] == '/')))
                    return true;
            }

            return false;
        }
    }
}